#include "accurateCommand.h"
#include <algorithm>

DRAMCommand::DRAMCommand()
{
    this->trans_id = 0;
    this->type = CommandType::INVALID;
    this->address = 0;
    this->bank_id = 0;
    this->rank_id = 0;
    this->bank_group_id = 0;
}

DRAMCommand::DRAMCommand(uint64_t trans_id, CommandType type, uint64_t address, uint64_t bank_id, uint64_t rank_id, uint64_t bank_group_id, uint64_t row_id, uint64_t column_id)
{
    this->trans_id = trans_id;
    this->type = type;
    this->address = address;
    this->bank_id = bank_id;
    this->rank_id = rank_id;
    this->bank_group_id = bank_group_id;
    this->row_id = row_id; 
    this->column_id = column_id;
}

CommandQueue::CommandQueue()
{
    this->cmd_queue_delay = 1;
    this->queue_policy = MyQueuePolicy::INVALID;
}

CommandQueue::CommandQueue(AddressMappingConfig *amp)
{
    this->cmd_queue_delay = 1;
    this->queue_policy = amp->getQueuePolicy();
    uint64_t queue_size = 0;

    std::cout << "rank num: " << amp->getRanks() 
              << ", bank groups: " << amp->getBankGroups() 
              << ", banks per group: " << amp->getBanksPerGroup() 
              << ", rows: " << amp->getRows() 
              << ", columns: " << amp->getColumns() 
              << std::endl;
    if (queue_policy == MyQueuePolicy::PER_BANK)
    {
        queue_size = amp->getRanks() * amp->getBanksPerGroup() * amp->getBankGroups();
        std::cout << "Queue size for PER_BANK policy: " << std::dec << queue_size << std::endl;
    }
    else if (queue_policy == MyQueuePolicy::PER_RANK)
    {
        queue_size = amp->getRanks();
        std::cout << "Queue size for PER_RANK policy: " << queue_size << std::endl;
    }
    else
    {
        std::cout << "Invalid queue policy: " << static_cast<int>(queue_policy) << std::endl;
    }
    queue.resize(queue_size);

    for (auto &q : queue)
    {
        q.clear();     // Ensure each vector is empty
        q.reserve(amp->getCmdQueueSize()); // Reserve space for 16 commands to avoid frequent reallocations
    }
}

bool CommandQueue::WillAcceptCommand(uint64_t rank_id, uint64_t bank_group_id, uint64_t bank_id)
{

    bool result = false;

    if (queue_policy == MyQueuePolicy::PER_BANK)
    {
        if (queue[bank_id * rank_id + bank_group_id].size() < queue[bank_id * rank_id + bank_group_id].capacity())
        {
            std::cout << "Queue size: " << queue[bank_id * rank_id + bank_group_id].size() 
                      << ", Capacity: " << queue[bank_id * rank_id + bank_group_id].capacity() << ", result: true" << std::endl;
            result = true;
        }
    }
    else
    {
        if (queue[rank_id].size() < queue[rank_id].capacity())
        {
            std::cout << "Queue size: " << queue[rank_id].size() 
                      << ", Capacity: " << queue[rank_id].capacity() << ", result: true" << std::endl;
            result = true;
        }
    }

    return result;
}

bool CommandQueue::AddCommand(DRAMCommand cmd)
{
    if (!WillAcceptCommand(cmd.rank_id, cmd.bank_group_id, cmd.bank_id))
    {
        return false; // Queue is full
    }

    if (queue_policy == MyQueuePolicy::PER_BANK)
    {
        queue[cmd.rank_id * cmd.bank_group_id + cmd.bank_id].push_back(cmd);
    }
    else if (queue_policy == MyQueuePolicy::PER_RANK)
    {
        queue[cmd.rank_id].push_back(cmd);
    }
    else
    {
        std::cout << "Invalid queue policy: " << static_cast<int>(queue_policy) << std::endl;
        return false;
    }

    std::cout << "Command added: Type=" << static_cast<int>(cmd.type) 
              << ", Address=" << cmd.address 
              << ", Rank ID=" << cmd.rank_id 
              << ", Bank Group ID=" << cmd.bank_group_id 
              << ", Bank ID=" << cmd.bank_id 
              << ", Row ID=" << cmd.row_id 
              << ", Column ID=" << cmd.column_id 
              << std::endl;

    return true;
}

DRAMCommand CommandQueue::GetCommand(uint64_t rank_id, uint64_t bank_group_id, uint64_t bank_id)
{
    DRAMCommand cmd;

    if (queue_policy == MyQueuePolicy::PER_BANK)
    {
        if (queue[rank_id * bank_group_id + bank_id].empty())
        {
            cmd.type = CommandType::INVALID; // No command available
            return cmd;
        }
        cmd = queue[rank_id * bank_group_id + bank_id].front();
    }
    else if (queue_policy == MyQueuePolicy::PER_RANK)
    {
        if (queue[rank_id].empty())
        {
            cmd.type = CommandType::INVALID; // No command available
            return cmd;
        }
        cmd = queue[rank_id].front();
    }

    return cmd;
}

uint64_t CommandQueue::Process(Transaction &trans, uint64_t time){
    uint64_t delay = 0;

    delay = this->cmd_queue_delay;

    std::cout << "CommandQueue::Processing transaction with ID: " << trans.getId() 
              << " at time: " << time 
              << " with delay: " << delay << std::endl;

    return delay;
}

uint64_t CommandQueue::Process(DRAMCommand cmd, uint64_t time){
    uint64_t delay = 0;

    delay = this->cmd_queue_delay;

    if(this->queue_policy == MyQueuePolicy::PER_RANK){
        queue[cmd.rank_id].push_back(cmd);
    }else{
        queue[cmd.rank_id * cmd.bank_group_id + cmd.bank_id].push_back(cmd);
    }

    std::cout << "CommandQueue::Processing command of type: " << static_cast<int>(cmd.type) 
              << " at time: " << time 
              << " with delay: " << std::dec << delay 
              << std::endl;

    return delay;
}


 DRAMCommand CommandQueue::Trans2CMD(Transaction &trans){

    TransactionType trans_type = trans.getType();
    CommandType cmd_type;
    switch (trans_type)
    {
    case TRANSACTION_TYPE::READ:
        cmd_type = CommandType::READ;
        break;
    default:
        cmd_type = CommandType::WRITE; // Assuming WRITE for all other cases
        break;
    }

    std::cout << "In CommandQueue::Trans2CMD: Converting Transaction ID: " << " Rank ID: " << trans.getRankId() 
              << ", Bank Group ID: " << trans.getBankGroupId() << ", Bank ID " << trans.getBankId() << std::endl;
    DRAMCommand cmd(trans.getId(), cmd_type, trans.getAddress(), trans.getBankId(), trans.getRankId(), trans.getBankGroupId(), trans.getRowId(), trans.getColumnId());

    std::cout << "Converted Transaction to DRAMCommand: Type=" << static_cast<int>(cmd.type) 
              << ", Address=" << cmd.address 
              << ", Rank ID=" << cmd.rank_id 
              << ", Bank Group ID=" << cmd.bank_group_id 
              << ", Bank ID=" << cmd.bank_id 
              << ", Row ID=" << cmd.row_id 
              << ", Column ID=" << cmd.column_id 
              << std::endl;
    return cmd;
 }


 bool CommandQueue::isEmpty() {
    if (queue_policy == MyQueuePolicy::PER_BANK) {
        for (const auto& q : queue) {
            if (!q.empty()) {
                return false; // At least one queue is not empty
            }
        }
    } else if (queue_policy == MyQueuePolicy::PER_RANK) {
        for (const auto& q : queue) {
            if (!q.empty()) {
                return false; // At least one queue is not empty
            }
        }
    }
    return true; // All queues are empty
 }

 void CommandQueue::DeleteCommand(uint64_t rank_id, uint64_t bank_group_id, uint64_t bank_id, uint64_t id){
    if (queue_policy == MyQueuePolicy::PER_BANK)
    {
        auto &cmds = queue[rank_id * bank_group_id + bank_id];
        cmds.erase(
        std::remove_if(cmds.begin(), cmds.end(),
                       [id](const DRAMCommand &t)
                       { return t.getTransId() == id; }),
        cmds.end());
    }else{
        auto &cmds = queue[rank_id];
        cmds.erase(
        std::remove_if(cmds.begin(), cmds.end(),
                       [id](const DRAMCommand &t)
                       { return t.getTransId() == id; }),
        cmds.end());
    }
 }
